iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
SideProject30

初探 Godot系列 第 25

[DAY 25] 組合 UI

  • 分享至 

  • xImage
  •  

今日目標:組合 UI 到主場景


▍事前準備

  1. 現在我們將 UI 加到我們的主頁面上,為了方便修改,我們將 player 場景的人物和移動輔助顯示分成兩個場景。因為移動顯示本來就是獨立的場景我們將 player 場景中的 CharacterBody2D 設為根節點然後將原本的 Node 節點和 instantiate 的 Movement 場景移除。

  2. 修改 player 腳本中計時器的初始化,因為 Timer 現在角色節點下。

	invincible_timer = $"InvincibleTimer"
	invincible_timer.timeout.connect(_on_invincible_timeout)
	slow_timer = $"SlowTimer"
	slow_timer.timeout.connect(_on_slow_timeout)
	stop_timer = $"StopTimer"
	stop_timer.timeout.connect(_on_stop_timeout)
  1. 最後再將人物和輔助顯示場景加到主場景中。

▍出發

  • 組合 UI

    1. 將昨天建立的 HUD 場景加到主場景下。

      # 結構如下:
      |--Main
      |   |--Background (Scene)
      |   |--MoveObstacleSpwanTimer (Timer)
      |   |--Player (Scene)
      |   |--MovementUI (Scene)
      |   |--HUD (Scene)
      
    2. 修改主場景程式將 handle_game_start 綁定到昨天建立的訊號上。

    var hud:CanvasLayer
    func _ready():
    # ...
        hud = $HUD
        hud.game_start.connect(handle_game_start)
    
  • 接著將所有需要遊戲開始運作的物件都實作在 handle_game_start 方法中。
    0. 宣告並初始化角色和背景到主場景根節點

    var background
    var player
    
    func _ready():
    # ...	
        background = $Background
        player = $Player
    
    1. 修改主場景腳本控制遊戲開始結束邏輯
    func handle_game_start():
        # 等待開始訊息顯示完畢
        await get_tree().create_timer(1.6).timeout
        background.start()
        player.start()
        await get_tree().create_timer(4).timeout
        move_obstacles_timer.start()
    
    func handle_game_end():
        move_obstacles_timer.stop()
        background.end()
        player.end()
        # 顯示結束畫面
        hud.show_game_over()
    
    1. 修改 background 場景:
    # 宣告我們的狀態並初始化為結束
    enum STATE {START, STOP, END}
    var State = STATE.END
    
    # 開始才執行邏輯
    func _process(delta):
        match State:
            STATE.START:
                scroll_background(background1)
                scroll_background(background2)
    
    # 更新狀態
    func start():
        State = STATE.START
    
    func stop():
        State = STATE.STOP
    
    # 結束時回復狀態
    func end():
        State = STATE.END
        # reset to beginning state
        background1.set_position(Vector2(0, 0))
        background2.set_position(Vector2(0, -viewpoint_height
        for bg in backgrounds_to_obstacles.keys():
            for obstacle in backgrounds_to_obstacles[bg]:
                obstacle.position = Vector2(0, -viewpoint_height*2)
    
    1. 修改角色腳本
    # 宣告我們的狀態並初始化為結束
    enum GAME_STATE {START, STOP, END}
    var game_state = GAME_STATE.END
    
    # 開始才執行邏輯
    func _process(delta):
        match game_state:
            GAME_STATE.START:
                # 原本的邏輯...
    
    func _input(event):
        match game_state:
            GAME_STATE.START:
                # 原本的邏輯...
    
    # 更新狀態
    func start():
        game_state = GAME_STATE.START
        # 開啟碰撞
        get_node("CollisionShape2D").disabled = false
    
    func stop():
        game_state = GAME_STATE.STOP
    
    func end():
        game_state = GAME_STATE.END
        player.position = start_position
        # 關閉碰撞
        get_node("CollisionShape2D").disabled = true
    
        # 重置所有狀態
        direction = Vector2.ZERO
        dragged = false
        player_anime.stop()
        movement_ui.visible = false
    
  • 設定我們的遊戲終止方式:碰撞到 move_obstacle 時結束遊戲。

    1. 調整我們的移動障礙場景中屬性面板的 Collision action,設置成 END
      https://ithelp.ithome.com.tw/upload/images/20231010/20162875nPZ8oPERBf.png
    2. 修改角色腳本處理 END 事件。
    signal game_end
    
    func handle_end_state():
        set_shader_para(0)
        game_end.emit()
    
    1. 修改主場景腳本綁定 handle_game_endgame_end 訊號上。
    func _ready():
    # ...
        player.game_end.connect(handle_game_end)
    
    1. 修改移動障礙物腳本,之前覆寫 ready 方法時忘記把邏輯補回,這樣才能接收到碰撞方法。
    func _ready():
    # ...
        body_entered.connect(_on_player_enter)
    

▍執行

Yes

▍完成

完整檔案(因為改的檔案有點多今天先貼 player 的程式)

extends CharacterBody2D

# shader
var anime_sprite = AnimatedSprite2D

# CharacterBody2D
var direction:Vector2
var dragged:bool = false
var oriPos: Vector2
var player: CharacterBody2D
var player_anime: AnimatedSprite2D

# MovementUI
var movement_ui: Node2D

# Collision Handling
@export var slow_speed:float = 1
@export var base_speed:float = 5

# start position
@export var start_position :Vector2 = Vector2(360, 1300)

var speed:float = base_speed

var invincible_timer:Timer
var slow_timer:Timer
var stop_timer:Timer
const DEFAULT = "DEFAULT"
var state:String = DEFAULT

# handle game state
enum GAME_STATE {START, STOP, END}
var game_state = GAME_STATE.END

# signal end state
signal game_end

# Called when the node enters the scene tree for the first time.
func _ready():
	
	anime_sprite = $AnimatedSprite2D
		
	movement_ui = $"../MovementUI"
	movement_ui.visible = false
	
	player = $"."
	player_anime = player.get_node("AnimatedSprite2D")
	
	# init player postition
	player.position = start_position
	
	invincible_timer = $"InvincibleTimer"
	invincible_timer.timeout.connect(_on_invincible_timeout)
	slow_timer = $"SlowTimer"
	slow_timer.timeout.connect(_on_slow_timeout)
	stop_timer = $"StopTimer"
	stop_timer.timeout.connect(_on_stop_timeout)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	match game_state:
		GAME_STATE.START:
			if dragged:
				player.position += direction*speed
			handle_player_state()

func _input(event):
	match game_state:
		GAME_STATE.START:
			if event is InputEventScreenTouch:
				
				if event.is_pressed():
					oriPos = event.position
					dragged = true
					player_anime.play()
					
					movement_ui.visible = true
					movement_ui.position = event.position
					
				elif event.is_released():
					direction = Vector2.ZERO
					dragged = false
					player_anime.stop()
					
					movement_ui.visible = false
					
			if event is InputEventScreenDrag:
				direction = (event.position - oriPos).normalized()
				movement_ui.rotation = Vector2.UP.angle_to(direction)
				if abs(direction.y) > abs(direction.x):
					player_anime.animation = "default"
				elif direction.x > 0:
					player_anime.animation = "turn_right"
				elif direction.x < 0:
					player_anime.animation = "turn_left"
			
func handle_player_state():
	match state:
		DEFAULT:
			pass
		"INVINCIBLE":
			handle_invincible_state()
		"SLOW":
			handle_slow_state()
		"STOP":
			handle_stop_state()
		"END":
			handle_end_state()
	state = DEFAULT

func handle_invincible_state():
	get_node("CollisionShape2D").disabled = true
	speed = base_speed
	set_shader_para(1, Vector4(0.98, 0.91, 0.66, 1), 7)
	invincible_timer.start()
	
func _on_invincible_timeout():
	set_shader_para(0)
	get_node("CollisionShape2D").disabled = false
	
func handle_slow_state():
	set_shader_para(1, Vector4(0.76, 0.96, 0.91, 1), 4)
	speed = slow_speed
	slow_timer.start()
	
func _on_slow_timeout():
	if speed == slow_speed:
		set_shader_para(0)
	speed = base_speed
	
func handle_stop_state():
	speed = 0
	set_shader_para(1, Vector4(0.97, 0.51, 0.58, 1), 1)
	stop_timer.start()
	
func _on_stop_timeout():
	set_shader_para(0)
	speed = base_speed
	
func handle_end_state():
	set_shader_para(0)
	game_end.emit()

func set_shader_para( mode: int, color: Vector4 =Vector4.ZERO, speed: float = 0):
	anime_sprite.material.set_shader_parameter("shine_color", color)
	anime_sprite.material.set_shader_parameter("cycle_speed", speed)
	anime_sprite.material.set_shader_parameter("mode", mode)
	
func start():
	game_state = GAME_STATE.START
	get_node("CollisionShape2D").disabled = false
	
func stop():
	game_state = GAME_STATE.STOP
	
func end():
	game_state = GAME_STATE.END
	player.position = start_position
	get_node("CollisionShape2D").disabled = true
	
	# reset
	direction = Vector2.ZERO
	dragged = false
	player_anime.stop()
	movement_ui.visible = false

:)


上一篇
[DAY 24] UI 設置 (CanvasLayer, Button, Label, await)
下一篇
[DAY 26] 分數設置 (get_ticks_msec)
系列文
初探 Godot30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言